import java.awt.*;
import java.net.URL;

/**
 * A class that represents a picture.  This class inherits from SimplePicture
 * 	and allows the student to add functionality and picture effects.  
 * 
 * @author Barb Ericson (ericson@cc.gatech.edu)
 * 	(Copyright Georgia Institute of Technology 2004)
 * @author Modified by Colleen Lewis (colleenl@berkeley.edu),
 * 	Jonathan Kotker (jo_ko_berkeley@berkeley.edu),
 * 	Kaushik Iyer (kiyer@berkeley.edu), George Wang (georgewang@berkeley.edu),
 * 	and David Zeng (davidzeng@berkeley.edu), for use in CS61BL, the data
 * 	structures course at University of California, Berkeley. 
 */
public class Picture extends SimplePicture 
{

	/////////////////////////// Static Variables //////////////////////////////

	// Different axes available to flip a picture.
	public static final int HORIZONTAL = 1;
	public static final int VERTICAL = 2;
	public static final int FORWARD_DIAGONAL = 3;
	public static final int BACKWARD_DIAGONAL = 4;

	// Different Picture objects for the bitmaps used in ASCII art conversion.
	private static Picture BMP_AMPERSAND;
	private static Picture BMP_APOSTROPHE;
	private static Picture BMP_AT;
	private static Picture BMP_BAR;
	private static Picture BMP_COLON; 
	private static Picture BMP_DOLLAR; 
	private static Picture BMP_DOT; 
	private static Picture BMP_EXCLAMATION; 
	private static Picture BMP_GRAVE; 
	private static Picture BMP_HASH;
	private static Picture BMP_PERCENT; 
	private static Picture BMP_SEMICOLON; 
	private static Picture BMP_SPACE; 	

	// myCount variable for the Iterator as well as nextIndexToReturn, and lastly
	// an array of pixels to keep for the paintBucket method. REMEMBER TO WIPE THE
	// ARRAY WHEN DONE WITH IT!
	private int myCount;
	private int nextIndexToReturn;
	private Pixel [ ] myPixels;
	private int myPixelsLength;
	

	//////////////////////////// Constructors /////////////////////////////////

	/**
	 * A constructor that takes no arguments.
	 */
	public Picture () {
		super();  
	}

	/**
	 * Creates a Picture from the file name provided.
	 * 
	 * @param fileName The name of the file to create the picture from.
	 */
	public Picture(String fileName) {
		// Let the parent class handle this fileName.
		super(fileName);
	}

	/**
	 * Creates a Picture from the width and height provided.
	 * 
	 * @param width the width of the desired picture.
	 * @param height the height of the desired picture.
	 */
	public Picture(int width, int height) {
		// Let the parent class handle this width and height.
		super(width, height);
	}

	/**
	 * Creates a copy of the Picture provided.
	 * 
	 * @param pictureToCopy Picture to be copied.
	 */
	public Picture(Picture pictureToCopy) {
		// Let the parent class do the copying.
		super(pictureToCopy);
	}

	/**
	 * Creates a copy of the SimplePicture provided.
	 * 
	 * @param pictureToCopy SimplePicture to be copied.
	 */
	public Picture(SimplePicture pictureToCopy) {
		// Let the parent class do the copying.
		super(pictureToCopy);
	}

	/////////////////////////////// Methods ///////////////////////////////////

	/**
	 * @return A string with information about the picture, such as 
	 * 	filename, height, and width.
	 */
	public String toString() {
		String output = "Picture, filename = " + this.getFileName() + "," + 
		" height = " + this.getHeight() + ", width = " + this.getWidth();
		return output;
	}

	/////////////////////// PROJECT 1 BEGINS HERE /////////////////////////////

	/* Each of the methods below is constructive: in other words, each of 
	 * 	the methods below generates a new Picture, without permanently
	 * 	modifying the original Picture. */

	//////////////////////////////// Level 1 //////////////////////////////////

	/**
	 * Converts the Picture into grayscale. Since any variation of gray
	 * 	is obtained by setting the red, green, and blue components to the same
	 * 	value, a Picture can be converted into its grayscale component
	 * 	by setting the red, green, and blue components of each pixel in the
	 * 	new picture to the same value: the average of the red, green, and blue
	 * 	components of the same pixel in the original.
	 * 
	 * @return A new Picture that is the grayscale version of this Picture.
	 */
	public Picture grayscale() {
		Picture newPicture = new Picture(this);

		int pictureHeight = this.getHeight();
		int pictureWidth = this.getWidth();

		for(int x = 0; x < pictureWidth; x++) {
			for(int y = 0; y < pictureHeight; y++) {
				newPicture.setPixelToGray(x, y);
			}
		}
		return newPicture;
	}

	/**
	 * Helper method for grayscale() to set a pixel at (x, y) to be gray.
	 * 
	 * @param x The x-coordinate of the pixel to be set to gray.
	 * @param y The y-coordinate of the pixel to be set to gray.
	 */
	private void setPixelToGray(int x, int y) {
		Pixel currentPixel = this.getPixel(x, y);
		int average = currentPixel.getAverage();
		currentPixel.setRed(average);
		currentPixel.setGreen(average);
		currentPixel.setBlue(average);		
	}

	/**
	 * Test method for setPixelToGray. This method is called by
	 * the JUnit file through the public method Picture.helpersWork().
	 */
	private static boolean setPixelToGrayWorks()
	{
		Picture bg           = Picture.loadPicture("Creek.bmp");
		Pixel focalPixel     = bg.getPixel(10, 10);
		bg.setPixelToGray(10, 10);
		int goalColor        = (int) focalPixel.getAverage();
		int originalAlpha    = focalPixel.getColor().getAlpha();
		boolean redCorrect   = focalPixel.getRed() == goalColor;
		boolean greenCorrect = focalPixel.getGreen() == goalColor; 
		boolean blueCorrect  = focalPixel.getBlue() == goalColor;
		boolean alphaCorrect = focalPixel.getAlpha() == originalAlpha;
		return redCorrect && greenCorrect && blueCorrect && alphaCorrect;
	}

	/**
	 * This method provide JUnit access to the testing methods written 
	 * within Picture.java
	 */
	public static boolean helpersWork() {
		if (!Picture.setPixelToGrayWorks())
		{return false;}
		if (!Picture.setPixelToNegativeWorks())
		{return false;}
		// You could put other tests here..

		return true;
	}

	/**
	 * Converts the Picture into its photonegative version. The photonegative
	 * 	version of an image is obtained by setting each of the red, green,
	 * 	and blue components of every pixel to a value that is 255 minus their
	 * 	current values.
	 * 
	 * @return A new Picture that is the photonegative version of this Picture. 
	 */
	public Picture negate() {
		// REPLACE THE CODE BELOW WITH YOUR OWN.

		// We want to give each pixel of the original picture "opposite" RGB values 
		// such that all three, red blue and green, look like new=(255-original). Using 
		// the grayscale() method as a blueprint:
		Picture newPicture = new Picture(this);

		int pictureHeight = this.getHeight();
		int pictureWidth = this.getWidth();

		for(int x = 0; x < pictureWidth; x++) {
			for(int y = 0; y < pictureHeight; y++) {
				newPicture.setPixelToNegative(x, y);
			}
		}
		return newPicture;	
	}
		// A method to set each	pixel to negative for the Picture method negative()
		private void setPixelToNegative(int x, int y)	{
			Pixel currentPixel = this.getPixel(x, y);
			currentPixel.setRed(255-currentPixel.getRed());
			currentPixel.setGreen(255-currentPixel.getGreen());
			currentPixel.setBlue(255-currentPixel.getBlue());	
		}

		private static boolean setPixelToNegativeWorks() {
					Picture bg           = Picture.loadPicture("Creek.bmp");
					Pixel focalPixel     = bg.getPixel(11, 11);
					int goalRed          = (int) 255-focalPixel.getRed();
					int goalBlue         = (int) 255-focalPixel.getBlue();
					int goalGreen        = (int) 255-focalPixel.getGreen();
					int originalAlpha    = focalPixel.getColor().getAlpha();
					bg.setPixelToNegative(11, 11);
					boolean redCorrect   = focalPixel.getRed() == goalRed;
					boolean greenCorrect = focalPixel.getGreen() == goalGreen; 
					boolean blueCorrect  = focalPixel.getBlue() == goalBlue;
					boolean alphaCorrect = focalPixel.getAlpha() == originalAlpha;
					return redCorrect && greenCorrect && blueCorrect && alphaCorrect;
				}

	
	/**
	 * Creates an image that is lighter than the original image. The range of
	 * each color component should be between 0 and 255 in the new image. The
	 * alpha value should not be changed.
	 * 
	 * @return A new Picture that has every color value of the Picture increased
	 *         by the lightenAmount.
	 */
	public Picture lighten(int lightenAmount) {
		// REPLACE THE CODE BELOW WITH YOUR OWN.
		/*
		 * This method lightens the picture on the frame by increasing its 
		 * three color component: red, blue and green. The amount of 
		 * increasing is passed in as a parameter. Note that the highest value
		 * of each color is 255. The color stops at 255 if lighten Amount overflows
		 */
		if ((lightenAmount < 0) || (lightenAmount > 255)){			
			throw new IllegalStateException("lighten amount is out of range. (0 ~ 255)");
		}
		//create a new picture copied from the original one.
		Picture lightenOne = new Picture(this);
		
		// Loop through all x.
		for (int x = 0; x < this.getWidth(); x++)
		{
			// Loop through all y.
			for (int y = 0; y < this.getHeight(); y++)
			{
				// if lighten amount is too much for red, set it to 255
				if (lightenOne.getPixel(x, y).getRed()+ lightenAmount >= 255){
					lightenOne.getPixel(x, y).setRed(255);
				}
				// else set red to be the sum of lighten amount and original value
				else{
					lightenOne.getPixel(x, y).setRed(getPixel(x, y).getRed()+ lightenAmount);
				}
				//  if lighten amount is too much for green set it to 255
				if (lightenOne.getPixel(x, y).getGreen()+ lightenAmount >= 255){
					lightenOne.getPixel(x, y).setGreen(255);
				}
				// else set green to be the sum of lighten amount and original value
				else{
					lightenOne.getPixel(x, y).setGreen(getPixel(x, y).getGreen()+ lightenAmount);
				}
				//  if lighten amount is too much for blue set it to 255
				if (lightenOne.getPixel(x, y).getBlue()+ lightenAmount >= 255){
					lightenOne.getPixel(x, y).setBlue(255);
				}
				// else set blue to be the sum of lighten amount and original value
				else{
					lightenOne.getPixel(x, y).setBlue(getPixel(x, y).getGreen()+ lightenAmount);
				}
			}
		}
		return lightenOne;
	}

	/**
	 * Creates an image that is darker than the original image.The range of
	 * each color component should be between 0 and 255 in the new image. The
	 * alpha value should not be changed.
	 * 
	 * @return A new Picture that has every color value of the Picture decreased
	 *         by the darkenenAmount.
	 */
	public Picture darken(int darkenAmount) {
		// REPLACE THE CODE BELOW WITH YOUR OWN.
		/*
		 * This method darken the picture on the frame by decreasing its 
		 * three color component: red, blue and green. The amount of 
		 * decreasing is passed in as a parameter. Note that the lowest value
		 * of each color is 0. The color goes down to zero if darken Amount
		 * exceeds its original value.
		 */
		if ((darkenAmount < 0) || (darkenAmount > 255)){			
			throw new IllegalStateException("darken Amount is out of range. (0 ~ 255)");
		}
		//create a new picture copied from the original one.
		Picture darkenOne = new Picture(this);
		// Loop through all x.
		for (int x = 0; x < this.getWidth(); x++)
		{
			// Loop through all y.
			for (int y = 0; y < this.getHeight(); y++)
			{
				// if darken amount is too much for red, set it to 0
				if (darkenOne.getPixel(x, y).getRed()- darkenAmount <= 0){
					darkenOne.getPixel(x, y).setRed(0);
				}
				// else set red to be the sum of darken amount and original value
				else{
					darkenOne.getPixel(x, y).setRed(getPixel(x, y).getRed()- darkenAmount);
				}
				//  if darken amount is too much for green set it to 255
				if (darkenOne.getPixel(x, y).getGreen()- darkenAmount <= 0){
					darkenOne.getPixel(x, y).setGreen(0);
				}
				// else set green to be the sum of darken amount and original value
				else{
					darkenOne.getPixel(x, y).setGreen(getPixel(x, y).getGreen()- darkenAmount);
				}
				//  if darken amount is too much for blue set it to 0
				if (darkenOne.getPixel(x, y).getBlue()+ darkenAmount <= 0){
					darkenOne.getPixel(x, y).setBlue(0);
				}
				// else set blue to be the sum of darken amount and original value
				else{
					darkenOne.getPixel(x, y).setBlue(getPixel(x, y).getGreen()- darkenAmount);
				}
			}
		}
		return darkenOne;
	}

	/**
	 * Creates an image where the blue value has been increased by amount.The range of
	 * each color component should be between 0 and 255 in the new image. The
	 * alpha value should not be changed.
	 * 
	 * @return A new Picture that has every blue value of the Picture increased
	 *         by amount.
	 */
	public Picture addBlue(int amount) {
		// REPLACE THE CODE BELOW WITH YOUR OWN.
		
		// If the amount is out of boundary, throw the Exception.
		if ((amount <= 0) || (amount >= 255)){			
			throw new IllegalStateException("Amount is out of range. (0 ~ 255)");
		}
		// Copy the picture to a new a picture
		Picture blueOne =new Picture(this);
		// Loop from x=0 to x get max 
		for (int x=0;x<this.getWidth();x++){
			// Loop from y=0 to y get max
			for (int y=0;y<this.getHeight();y++){
				// Assign 255 to the color if the sum is out of boundary
				if (blueOne.getPixel(x, y).getBlue()+ amount >= 255){
					blueOne.getPixel(x, y).setBlue(255);
				}
				// Assign the sum to the color
				else {
					blueOne.getPixel(x, y).setBlue(blueOne.getPixel(x, y).getBlue()+amount);
				}
			}
		}
		return blueOne;
	}
	
	/**
	 * Creates an image where the red value has been increased by amount. The range of
	 * each color component should be between 0 and 255 in the new image. The
	 * alpha value should not be changed.
	 * 
	 * @return A new Picture that has every red value of the Picture increased
	 *         by amount.
	 */
	public Picture addRed(int amount) {
		// REPLACE THE CODE BELOW WITH YOUR OWN.
		
		// If the amount is out of boundary, throw the Exception.
		if ((amount <= 0) || (amount >= 255)){			
			throw new IllegalStateException("Amount is out of range. (0 ~ 255)");
		}
		// Copy the picture to a new a picture
		Picture redOne =new Picture(this);
		// Loop from x=0 to x get max 
		for (int x=0;x<this.getWidth();x++){
			// Loop from y=0 to y get max
			for (int y=0;y<this.getHeight();y++){
				// Assign 255 to the color if the sum is out of boundary
				if (redOne.getPixel(x, y).getRed()+ amount >= 255){
					redOne.getPixel(x, y).setRed(255);
				}
				// Assign the sum to the color
				else {
					redOne.getPixel(x, y).setRed(redOne.getPixel(x, y).getRed()+amount);
				}
			}
		}
		return redOne;
	}
	
	/**
	 * Creates an image where the green value has been increased by amount. The range of
	 * each color component should be between 0 and 255 in the new image. The
	 * alpha value should not be changed.
	 * 
	 * @return A new Picture that has every green value of the Picture increased
	 *         by amount.
	 */
	public Picture addGreen(int amount) {
		// REPLACE THE CODE BELOW WITH YOUR OWN.
		
		// If the amount is out of boundary, throw the Exception.
		if ((amount <= 0) || (amount >= 255)){			
			throw new IllegalStateException("Amount is out of range. (0 ~ 255)");
		}
		// Copy the picture to a new a picture
		Picture greenOne =new Picture(this);
		// Loop from x=0 to x get max 
		for (int x=0;x<this.getWidth();x++){
			// Loop from y=0 to y get max
			for (int y=0;y<this.getHeight();y++){
				// Assign 255 to the color if the sum is out of boundary
				if (greenOne.getPixel(x, y).getGreen()+ amount >= 255){
					greenOne.getPixel(x, y).setGreen(255);
				}
				// Assign the sum to the color
				else {
					greenOne.getPixel(x, y).setGreen(greenOne.getPixel(x, y).getGreen()+amount);
				}
			}
		}
		return greenOne;
	}
	
	/** 
	 * @param x x-coordinate of the pixel currently selected.
	 * @param y y-coordinate of the pixel currently selected.
	 * @param background Picture to use as the background.
	 * @param threshold Threshold within which to replace pixels.
	 * 
	 * @return A new Picture where all the pixels in the original Picture,
	 * 	which differ from the currently selected pixel within the provided
	 * 	threshold (in terms of color distance), are replaced with the
	 * 	corresponding pixels in the background picture provided.
	 * 
	 * 	If the two Pictures are of different dimensions, the new Picture will
	 * 	have length equal to the smallest of the two Pictures being combined,
	 * 	and height equal to the smallest of the two Pictures being combined.
	 * 	In this case, the Pictures are combined as if they were aligned at
	 * 	the top left corner (0, 0).
	 */
	public Picture chromaKey(int xRef, int yRef, Picture background, int threshold) {
		/* REPLACE THE CODE BELOW WITH YOUR OWN. */
		
		/* The method deals with each pixel of an image and if the pixel's 
	 	 * distance to the reference pixel is within the threshold, we replace 
	 	 * the pixel with its corresponding pixel from the background. To check
	 	 * this pixel distance, we use the pixel method colorDistance() and
	 	 * see if the distance is within the threshold (assuming that we accept
	 	 * values less than but not equal to the threshold).
		 */
		int pictureHeight; 
		int pictureWidth;
		// the new height and width will be determined by the if-else statements
		// so that the minimum height of the two pictures is used for the new pic,
		// likewise with the width
		int thisPictureHeight = this.getHeight(); 
		int backgroundPictureHeight = background.getHeight();
		int thisPictureWidth = this.getWidth(); 
		int backgroundPictureWidth = background.getWidth();
		
		if (backgroundPictureHeight<thisPictureHeight) {
			pictureHeight = backgroundPictureHeight;
		} else {
			pictureHeight = thisPictureHeight;
		}
		if (backgroundPictureWidth<thisPictureWidth) {
			pictureWidth = backgroundPictureWidth;
		} else {
			pictureWidth = thisPictureWidth;
		}
		Picture newPicture = new Picture(pictureWidth, pictureHeight);
		
		Color referenceColor=this.getPixel(xRef, yRef).getColor();
		// iterating over the entire new picture;
		for(int x = 0; x < pictureWidth; x++) {
			for(int y = 0; y < pictureHeight; y++) {
				// compare the current pixel's color to that of the reference pixel
				// using colorDistance(), define threshold, cast the distance as an 
				// int before seeing if we are within the threshold.
				Pixel currentPixel=this.getPixel(x,y);
				if ((int)(currentPixel.colorDistance(referenceColor))<threshold) {
					newPicture.getPixel(x,y).setColor(background.getPixel(x,y).getColor());
				} else {
					newPicture.getPixel(x,y).setColor(currentPixel.getColor());
				}
			}
		}
		return newPicture;
	}

	//////////////////////////////// Level 2 //////////////////////////////////

	/**
	 * Rotates this Picture by the integer multiple of 90 degrees provided.
	 * 	If the number of rotations provided is positive, then the picture
	 * 	is rotated clockwise; else, the picture is rotated counterclockwise.
	 * 	Multiples of four rotations (including zero) correspond to no
	 * 	rotation at all.
	 * 
	 * @param rotations The number of 90-degree rotations to rotate this
	 * 	image by.
	 * 
	 * @return A new Picture that is the rotated version of this Picture.
	 */
	public Picture rotate(int rotations) {
		if (rotations==1) {
			int pictureHeight = this.getWidth();
			int pictureWidth = this.getHeight();
			Picture newPicture = new Picture(pictureWidth,pictureHeight);
			//SimplePicture ensures each pixel exists, so we iterate over the picture:
			for(int x = 0; x < pictureWidth; x++) {
				for(int y = 0; y < pictureHeight; y++) {
					// sort of a rotation matrix, we treat the new pixel as having
					// coordinates (y,-x) corresponding to the original x and y,
					// though don't quote me on that, I haven't taken math 54 yet.
					newPicture.getPixel(x,y).setColor(this.getPixel(y,pictureWidth-(x+1)).getColor());
				}
			}
			return newPicture;
			// now this handles all rotation input possibilities, large, small, negative too
		} else if (rotations>1) { 
			rotations = rotations % 4;
			Picture tempPic = new Picture(this);
			 return tempPic.rotate(1).rotate(rotations-1);
		} else if (rotations<1){
			rotations = rotations % 4;
			Picture tempPic = new Picture(this);
			return tempPic.rotate(3).rotate(rotations+1);
		} else { //rotations == 0:
			return this;
		}
	}


	/**
	 * Flips this Picture about the given axis. The axis can be one of
	 * 	four static integer constants:
	 * 
	 * 	(a) Picture.HORIZONTAL: The picture should be flipped about
	 * 		a horizontal axis passing through the center of the picture.
	 * 	(b) Picture.VERTICAL: The picture should be flipped about
	 * 		a vertical axis passing through the center of the picture.
	 * 	(c) Picture.FORWARD_DIAGONAL: The picture should be flipped about
	 * 		an axis that passes through the north-east and south-west
	 * 		corners of the picture.
	 * 	(d) Picture.BACKWARD_DIAGONAL: The picture should be flipped about
	 * 		an axis that passes through the north-west and south-east
	 * 		corners of the picture.
	 * 
	 * @param axis Axis about which to flip the Picture provided.
	 * 
	 * @return A new Picture flipped about the axis provided.
	 */
	public Picture flip(int axis) {
		// REPLACE THE CODE BELOW WITH YOUR OWN.
		Picture newOne = new Picture (this);
		// Horizontal
		if (axis==1){
			for (int x=0;x<this.getWidth();x++){
				for (int y=0;y<this.getHeight();y++){
					newOne.getPixel(x,y).setColor(this.getPixel(x,this.getHeight()-y-1).getColor());
				}
			}
		}
		// Vertical
		if (axis==2){
			for (int x=0;x<this.getWidth();x++){
				for (int y=0;y<this.getHeight();y++){
					// 
					newOne.getPixel(x,y).setColor(this.getPixel(this.getWidth()-x-1,y).getColor());
				}
			}
		}
		// Forward
		if (axis==3){
			newOne=newOne.flip(1);
			newOne=newOne.rotate(3);
		}
		// Backward
		if (axis==4){
			newOne=newOne.flip(2);
			newOne=newOne.rotate(3);
		}
		return newOne;
	}

	/**
	 * @param threshold
	 *            Threshold to use to determine the presence of edges.
	 * 
	 * @return A new Picture that contains only the edges of this Picture. For
	 *         each pixel, we separately consider the color distance between
	 *         that pixel and the one pixel to its left, and also the color
	 *         distance between that pixel and the one pixel to the north, where
	 *         applicable. As an example, we would compare the pixel at (3, 4)
	 *         with the pixels at (3, 3) and the pixels at (2, 4). Also, since
	 *         the pixel at (0, 4) only has a pixel to its north, we would only
	 *         compare it to that pixel. If either of the color distances is
	 *         larger than the provided color threshold, it is set to black
	 *         (with an alpha of 255); otherwise, the pixel is set to white
	 *         (with an alpha of 255). The pixel at (0, 0) will always be set to
	 *         white.
	 */
	public Picture showEdges(int threshold) {
		/* REPLACE THE CODE BELOW WITH YOUR OWN. */
		/*
		 *  In this method, the corner cases are handled first. 
		 *  upper-left corner, with the coordinates (0,0) is always set to white;
		 *  leftmost  lane excluding (0,0) only compare to its north;
		 *  uppermost lane excluding (0,0) only compare to its left;
		 *  Then the rest part of this picture both compare to left and north.
		 *  
		 *  Whenever a left or north pixel to compare has a distance larger
		 *  than threshold,  the present pixel will be set to black. Otherwise, 
		 *  it is set to white. 
		 */
		//throw an exception if input is not valid
		if ((threshold < 0) || (threshold > 100)){			
			throw new IllegalStateException("threshold is out of range. (0 ~ 100)");
		}
		//create a new picture copied from original one
		Picture showEdges = new Picture(this);
		//make the upper-left corner always white
		showEdges.getPixel(0, 0).setAlpha(255);
		showEdges.getPixel(0, 0).setColor(new Color(255,255,255));
		//make the leftmost lane only compare to the north.
		for (int y = 1; y < this.getHeight(); y++){
			if ((int)this.getPixel(0, y).colorDistance(this.getPixel(0, y-1).getColor())>threshold){
				showEdges.getPixel(0, y).setAlpha(255);
				showEdges.getPixel(0, y).setColor(new Color(0,0,0));
			}
			else{
				showEdges.getPixel(0, y).setAlpha(255);
				showEdges.getPixel(0, y).setColor(new Color(255,255,255));
			}
		}
		// make the uppermost lane only compare to the left
		for (int x = 1; x < this.getWidth(); x++){
			if ((int)this.getPixel(x, 0).colorDistance(this.getPixel(x-1, 0).getColor())>(threshold)){
				showEdges.getPixel(x, 0).setAlpha(255);
				showEdges.getPixel(x, 0).setColor(new Color(0,0,0));
			}
			else{
				showEdges.getPixel(x, 0).setAlpha(255);
				showEdges.getPixel(x, 0).setColor(new Color(255,255,255));
			}
		}
		//make the rest compare both to north and to left
		// Loop through the rest area
		for (int x = 1; x < this.getWidth(); x++)
		{
			// Loop through y=1 to y max.
			for (int y = 1; y < this.getHeight(); y++)
			{
				if ((int)this.getPixel(x, y).colorDistance(this.getPixel(x, y-1).getColor())>(threshold)){
					showEdges.getPixel(x, y).setAlpha(255);
					showEdges.getPixel(x, y).setColor(new Color(0,0,0));
				}
				else if ((int)this.getPixel(x, y).colorDistance(this.getPixel(x-1, y).getColor())>threshold) {
					showEdges.getPixel(x, y).setAlpha(255);
					showEdges.getPixel(x, y).setColor(new Color(0,0,0));
				}
				else{
					showEdges.getPixel(x, y).setAlpha(255);
					showEdges.getPixel(x, y).setColor(new Color(255,255,255));
				}
			}
		}
		
		
		return showEdges;
	}

	//////////////////////////////// Level 3 //////////////////////////////////

	/**
	 * @return A new Picture that is the ASCII art version of this Picture. To
	 *         implement this, the Picture is first converted into its grayscale
	 *         equivalent. Then, starting from the top left, the average color
	 *         of every chunk of 10 pixels wide by 20 pixels tall is computed.
	 *         Based on the average value obtained, this chunk will be replaced
	 *         by the corresponding ASCII character specified by the table
	 *         below.
	 *
	 *	       The ASCII characters to be used are available as Picture objects,
	 * 	       also of size 10 pixels by 20 pixels. The following characters
	 * 	       should be used, depending on the average value obtained:
	 * 	
	 * 	0 to 18: # (Picture.BMP_POUND)
	 * 	19 to 37: @ (Picture.BMP_AT)
	 * 	38 to 56: & (Picture.BMP_AMPERSAND)
	 * 	57 to 75: $ (Picture.BMP_DOLLAR)
	 * 	76 to 94: % (Picture.BMP_PERCENT)
	 * 	95 to 113: | (Picture.BMP_BAR)
	 * 	114 to 132: ! (Picture.BMP_EXCLAMATION)
	 * 	133 to 151: ; (Picture.BMP_SEMICOLON)
	 * 	152 to 170: : (Picture.BMP_COLON)
	 * 	171 to 189: ' (Picture.BMP_APOSTROPHE)
	 * 	190 to 208: ` (Picture.BMP_GRAVE)
	 * 	209 to 227: . (Picture.BMP_DOT)
	 * 	228 to 255: (Picture.BMP_SPACE)
	 * 
	 * We provide a getAsciiPic method to obtain the Picture object 
	 * 	corresponding to a character, given any of the static Strings
	 * 	mentioned above.
	 * 
	 * Note that the resultant Picture should be the exact same size
	 * 	as the original Picture; this might involve characters being
	 * 	partially copied to the final Picture. 
	 */
	public Picture convertToAscii() {
		/* REPLACE THE CODE BELOW WITH YOUR OWN. */
		/*
		 * Basically, this method transfer a normal picture into collection of Ascii 
		 * pictures depending on the gray value. The very first thing I will do 
		 * is to break the whole picture into chunks. The grayscale() method and
		 * getGrayValue method(which is created by us) are called to determine
		 * the gray value in each chunk. Then I used the graphic command to 
		 * redraw Ascii pictures on my frame.
		 */
		//Create a new picture
		Picture convertToAscii = new Picture(this);
		//put it into grayscale
		convertToAscii = convertToAscii.grayscale();
		//divide the picture into chunks
		int chunkRow,chunkCol;
		
		chunkRow = (int)convertToAscii.getHeight()/20;
		if ((int)convertToAscii.getHeight()%20 != 0){
			chunkRow++;
		}
		chunkCol = (int)convertToAscii.getWidth()/10;
		if ((int)convertToAscii.getWidth()%10 != 0){
			chunkCol++;
		}
		//Calculate the gray value in each chunk and replace the picture
		//Convert the picture based on its gray value
		Graphics2D graphics2d = ( convertToAscii.getBufferedImage()).createGraphics();
		for (int x=0;x<=(chunkCol-1)*10;x=x+10){
			for (int y=0;y<=(chunkRow-1)*20;y=y+20){
				Image image = getAsciiPic(getGrayValue(x,y)).getImage();
				
				graphics2d.drawImage(image, x, y, null);
				
			}
		}
		
		return convertToAscii;
	}
	/**
	 * @param  x x-coordinate of the selected pixel which is
	 * 			the left top corner of a 10 pixels wide by 20 pixels 
	 * 			chunk.
	 * 			
	 * @param  y y-coordinate of the selected pixel which is
	 * 			the left top corner of a 10 pixels wide by 20 pixels 
	 * 			chunk.
	 * 
	 * @return an integer value which is the average gray value
	 * 		   of the 10 pixels wide by 20 pixels tall chunk determined
	 * 			by the parameters.
	 * 
	 * This function is created by me to as helper method to 
	 * "converToAscii()". The returned gray value can be used
	 * to determine which ASCII is suit for this chunk. if the selected
	 * chunk exceeds the frame of picture, only the average of 
	 * those within the frame will be returned.
	 */
	public int getGrayValue(int x, int y){
		int grayValue = 0;
		int counter = 0;
		for (int i = x;i<x+10;i++){
			for (int j = y; j<y+20;j++){
				if ((i<this.getWidth())&&(j<this.getHeight())){
					//sum up grayValue of all exists pixel
					grayValue += this.getPixel(i, j).getAverage();
					counter++;
				}//if the chunk exceed the frame, only compute the average
				 //value of the area within the frame.
					
						
			}
		}
		//Compute the average
		grayValue = grayValue / counter;
		
		
		return grayValue;
		
	}
	/**
	 * Blurs this Picture. To achieve this, the algorithm takes a pixel, and
	 * sets it to the average value of all the pixels in a square of side (2 *
	 * blurThreshold) + 1, centered at that pixel. For example, if blurThreshold
	 * is 2, and the current pixel is at location (8, 10), then we will consider
	 * the pixels in a 5 by 5 square that has corners at pixels (6, 8), (10, 8),
	 * (6, 12), and (10, 12). If there are not enough pixels available -- if the
	 * pixel is at the edge, for example, or if the threshold is larger than the
	 * image -- then the missing pixels are ignored, and the average is taken
	 * only of the pixels available.
	 * 
	 * The red, blue, green and alpha values should each be averaged separately.
	 * 
	 * @param blurThreshold
	 *            Size of the blurring square around the pixel.
	 * 
	 * @return A new Picture that is the blurred version of this Picture, using
	 *         a blurring square of size (2 * threshold) + 1.
	 */

	public Picture blur(int blurThreshold) {
		/* REPLACE THE CODE BELOW WITH YOUR OWN. */
		Picture NewOne = new Picture(this);		
		for (int x=0;x<blurThreshold;x++){			
			for (int y=0;y<blurThreshold;y++){				
				// Sum up all the pixels in the square
				int alpha=0,blue=0,red=0,green=0;
				for (int p=0;p<=x+blurThreshold;p++){
					for (int q=0;q<=y+blurThreshold;q++){
						alpha+=this.getPixel(p, q).getAlpha();
						blue+=this.getPixel(p, q).getBlue();
						red+=this.getPixel(p, q).getRed();
						green+=this.getPixel(p,q).getGreen();
					}	
				}
				// Set the value to calculated average
				NewOne.getPixel(x, y).setAlpha((int) alpha/((blurThreshold+x+1)*(blurThreshold+y+1)));
				NewOne.getPixel(x, y).setBlue((int) blue/((blurThreshold+x+1)*(blurThreshold+y+1)));
				NewOne.getPixel(x, y).setRed((int) red/((blurThreshold+x+1)*(blurThreshold+y+1)));
				NewOne.getPixel(x, y).setGreen((int) green/((blurThreshold+x+1)*(blurThreshold+y+1)));
			}			
			for (int y=blurThreshold;y<this.getHeight()-blurThreshold-1;y++){
				int alpha=0,blue=0,red=0,green=0;
				for (int p=0;p<=x+blurThreshold;p++){
					for (int q=y-blurThreshold;q<=y+blurThreshold;q++){
						alpha+=this.getPixel(p, q).getAlpha();
						blue+=this.getPixel(p, q).getBlue();
						red+=this.getPixel(p, q).getRed();
						green+=this.getPixel(p,q).getGreen();
					}
				}
				NewOne.getPixel(x, y).setAlpha((int) alpha/((blurThreshold+x+1)*(2*blurThreshold+1)));
				NewOne.getPixel(x, y).setBlue((int) blue/((blurThreshold+x+1)*(2*blurThreshold+1)));
				NewOne.getPixel(x, y).setRed((int) red/((blurThreshold+x+1)*(2*blurThreshold+1)));
				NewOne.getPixel(x, y).setGreen((int) green/((blurThreshold+x+1)*(2*blurThreshold+1)));
			}			
			for (int y=this.getHeight()-blurThreshold-1;y<this.getHeight();y++){
				int alpha=0,blue=0,red=0,green=0;
				for (int p=0;p<=x+blurThreshold;p++){
					for (int q=y-blurThreshold;q<this.getHeight();q++){
						alpha+=this.getPixel(p, q).getAlpha();
						blue+=this.getPixel(p, q).getBlue();
						red+=this.getPixel(p, q).getRed();
						green+=this.getPixel(p,q).getGreen();
					}
				}
				NewOne.getPixel(x, y).setAlpha((int) alpha/((blurThreshold+x+1)*(this.getHeight()-y+blurThreshold)));
				NewOne.getPixel(x, y).setBlue((int) blue/((blurThreshold+x+1)*(this.getHeight()-y+blurThreshold)));
				NewOne.getPixel(x, y).setRed((int) red/((blurThreshold+x+1)*(this.getHeight()-y+blurThreshold)));
				NewOne.getPixel(x, y).setGreen((int) green/((blurThreshold+x+1)*(this.getHeight()-y+blurThreshold)));
			}
			
		}				
		for (int x=blurThreshold;x<this.getWidth()-blurThreshold;x++){			
			for (int y=0;y<blurThreshold;y++){
				int alpha=0,blue=0,red=0,green=0;
					for (int p=x-blurThreshold;p<=x+blurThreshold;p++){
						for (int q=0;q<=y+blurThreshold;q++){
							alpha+=this.getPixel(p, q).getAlpha();
							blue+=this.getPixel(p, q).getBlue();
							red+=this.getPixel(p, q).getRed();
							green+=this.getPixel(p,q).getGreen();
						}
					}
					NewOne.getPixel(x, y).setAlpha((int) alpha/((2*blurThreshold+1)*(blurThreshold+y+1)));
					NewOne.getPixel(x, y).setBlue((int) blue/((2*blurThreshold+1)*(blurThreshold+y+1)));
					NewOne.getPixel(x, y).setRed((int) red/((2*blurThreshold+1)*(blurThreshold+y+1)));
					NewOne.getPixel(x, y).setGreen((int) green/((2*blurThreshold+1)*(blurThreshold+y+1)));
			}			
			for (int y=blurThreshold;y<this.getHeight()-blurThreshold;y++){
				int alpha=0,blue=0,red=0,green=0;
				for (int p=x-blurThreshold;p<=x+blurThreshold;p++){
					for (int q=y-blurThreshold;q<=y+blurThreshold;q++){
						alpha+=this.getPixel(p, q).getAlpha();
						blue+=this.getPixel(p, q).getBlue();
						red+=this.getPixel(p, q).getRed();
						green+=this.getPixel(p,q).getGreen();
					}
				}
				NewOne.getPixel(x, y).setAlpha((int) alpha/((2*blurThreshold+1)*(2*blurThreshold+1)));
				NewOne.getPixel(x, y).setBlue((int) blue/((2*blurThreshold+1)*(2*blurThreshold+1)));
				NewOne.getPixel(x, y).setRed((int) red/((2*blurThreshold+1)*(2*blurThreshold+1)));
				NewOne.getPixel(x, y).setGreen((int) green/((2*blurThreshold+1)*(2*blurThreshold+1)));
			}			
			for (int y=this.getHeight()-blurThreshold;y<this.getHeight();y++){
				int alpha=0,blue=0,red=0,green=0;
				for (int p=x-blurThreshold;p<=x+blurThreshold;p++){
					for (int q=y-blurThreshold;q<this.getHeight();q++){
						alpha+=this.getPixel(p, q).getAlpha();
						blue+=this.getPixel(p, q).getBlue();
						red+=this.getPixel(p, q).getRed();
						green+=this.getPixel(p,q).getGreen();
					}
				}
				NewOne.getPixel(x, y).setAlpha((int) alpha/((2*blurThreshold+1)*(this.getHeight()-y+blurThreshold)));
				NewOne.getPixel(x, y).setBlue((int) blue/((2*blurThreshold+1)*(this.getHeight()-y+blurThreshold)));
				NewOne.getPixel(x, y).setRed((int) red/((2*blurThreshold+1)*(this.getHeight()-y+blurThreshold)));
				NewOne.getPixel(x, y).setGreen((int) green/((2*blurThreshold+1)*(this.getHeight()-y+blurThreshold)));
			}			
		}		
		for (int x=this.getWidth()-blurThreshold;x<this.getWidth();x++){			
			for (int y=0;y<blurThreshold;y++){
				int alpha=0,blue=0,red=0,green=0;
					for (int p=x-blurThreshold;p<this.getWidth();p++){
						for (int q=0;q<=y+blurThreshold;q++){
							alpha+=this.getPixel(p, q).getAlpha();
							blue+=this.getPixel(p, q).getBlue();
							red+=this.getPixel(p, q).getRed();
							green+=this.getPixel(p,q).getGreen();
						}
					}
					NewOne.getPixel(x, y).setAlpha((int) alpha/((this.getWidth()-x+blurThreshold)*(blurThreshold+y+1)));
					NewOne.getPixel(x, y).setBlue((int) blue/((this.getWidth()-x+blurThreshold)*(blurThreshold+y+1)));
					NewOne.getPixel(x, y).setRed((int) red/((this.getWidth()-x+blurThreshold)*(blurThreshold+y+1)));
					NewOne.getPixel(x, y).setGreen((int) green/((this.getWidth()-x+blurThreshold)*(blurThreshold+y+1)));
			}			
			for (int y=blurThreshold;y<this.getHeight()-blurThreshold-1;y++){
				int alpha=0,blue=0,red=0,green=0;
				for (int p=x-blurThreshold;p<=this.getWidth()-1;p++){
					for (int q=y-blurThreshold;q<=y+blurThreshold;q++){
						alpha+=this.getPixel(p, q).getAlpha();
						blue+=this.getPixel(p, q).getBlue();
						red+=this.getPixel(p, q).getRed();
						green+=this.getPixel(p,q).getGreen();
					}
				}
				NewOne.getPixel(x, y).setAlpha((int) alpha/((this.getWidth()-x+blurThreshold)*(2*blurThreshold+1)));
				NewOne.getPixel(x, y).setBlue((int) blue/((this.getWidth()-x+blurThreshold)*(2*blurThreshold+1)));
				NewOne.getPixel(x, y).setRed((int) red/((this.getWidth()-x+blurThreshold)*(2*blurThreshold+1)));
				NewOne.getPixel(x, y).setGreen((int) green/((this.getWidth()-x+blurThreshold)*(2*blurThreshold+1)));
			}			
			for (int y=this.getHeight()-blurThreshold-1;y<this.getHeight();y++){
				int alpha=0,blue=0,red=0,green=0;
				for (int p=x-blurThreshold;p<=this.getWidth()-1;p++){
					for (int q=y-blurThreshold;q<this.getHeight();q++){
						alpha+=this.getPixel(p, q).getAlpha();
						blue+=this.getPixel(p, q).getBlue();
						red+=this.getPixel(p, q).getRed();
						green+=this.getPixel(p,q).getGreen();
					}
				}
				NewOne.getPixel(x, y).setAlpha((int) alpha/((this.getWidth()-x+blurThreshold)*(this.getHeight()-y+blurThreshold)));
				NewOne.getPixel(x, y).setBlue((int) blue/((this.getWidth()-x+blurThreshold)*(this.getHeight()-y+blurThreshold)));
				NewOne.getPixel(x, y).setRed((int) red/((this.getWidth()-x+blurThreshold)*(this.getHeight()-y+blurThreshold)));
				NewOne.getPixel(x, y).setGreen((int) green/((this.getWidth()-x+blurThreshold)*(this.getHeight()-y+blurThreshold)));
			}
		}
		return NewOne;
	}


	/**
	 * @param x x-coordinate of the pixel currently selected.
	 * @param y y-coordinate of the pixel currently selected.
	 * @param threshold Threshold within which to delete pixels.
	 * @param newColor New color to color pixels.
	 * 
	 * @return A new Picture where all the pixels connected to the currently
	 * 	selected pixel, and which differ from the selected pixel within the
	 * 	provided threshold (in terms of color distance), are colored with
	 * 	the new color provided. 
	 */
	public Picture paintBucket(int x, int y, int threshold, Color newColor) {
		/* REPLACE THE CODE BELOW WITH YOUR OWN. */
		Picture newPicture = new Picture(this);
		Color referenceColor = newPicture.getPixel(x,y).getColor();
		newPicture.initIterator();
		newPicture.add(x, y, referenceColor, threshold, newColor);
		int xCurrent; int yCurrent; Pixel currentPixel;
		while (newPicture.hasNext()) {
			// Going to Implement an orientation?? jk, not happening
			
			currentPixel=newPicture.next();
			xCurrent = currentPixel.getX(); 
			yCurrent = currentPixel.getY();
			// checking the eight pixels surrounding the current pixel, first making sure they
			// exist as pixels, then using the tests in add() to see if they get on the list.
			//orientations:
			// |1|2|3|
			// |8|0|4|
			// |7|6|5|
			//we go around the orientations and decide who checks which orientation and what
			//orientation it receives, so far though, I am not going to try unless i feel 
			//really ambitious.

			// Four Edges:
			if (yCurrent+1<newPicture.getHeight()) {
				//orientation 6
					newPicture.add(xCurrent, yCurrent+1, referenceColor, threshold, newColor);
				}	
			if (xCurrent+1<newPicture.getWidth()) {
				//orientation 4
					newPicture.add(xCurrent+1, yCurrent, referenceColor, threshold, newColor);
				}
			if (yCurrent-1>=0) { 
				//orientation 2
					newPicture.add(xCurrent, yCurrent-1, referenceColor, threshold, newColor);
			}
			if (xCurrent-1>=0) {
				//orientation 8
					newPicture.add(xCurrent-1, yCurrent, referenceColor, threshold, newColor);	
			}
			// Four Diagonals:
			if (xCurrent+1<newPicture.getWidth() && yCurrent+1<newPicture.getHeight()) {
				//orientation 5
					newPicture.add(xCurrent+1, yCurrent+1, referenceColor, threshold, newColor);	
			}		
			if (xCurrent-1>=0 && yCurrent+1<newPicture.getHeight()) {
				//orientation 7
					newPicture.add(xCurrent-1, yCurrent+1, referenceColor, threshold, newColor);
			}		
			if (xCurrent+1<newPicture.getWidth() && yCurrent-1>=0) {
				//orientation 3
					newPicture.add(xCurrent+1, yCurrent-1, referenceColor, threshold, newColor);
			}
			if (xCurrent-1>=0 && yCurrent-1>=0) {	
				//orientation 1
					newPicture.add(xCurrent-1, yCurrent-1, referenceColor, threshold, newColor);
			}
		}
		return newPicture;
	}
	
	///////////	  /////////////	  /////////////	  /////////////	  /////////////	  /////////////
	private void add (int x, int y,Color ref,int threshold, Color newColor) {
		Pixel pixelToBeAdded=this.getPixel(x, y);
		if (this.myCount>=this.myPixelsLength) {
			System.err.println("input method add() failed");
			System.exit(1);
		} else if ((int) this.getPixel(x,y).colorDistance(newColor)<threshold) {
			//do nothing at all... I think this is safest.
		} else if (this.myCount<this.myPixelsLength      // the array has room
//				&& !this.contains(this.getPixel(x,y))         // the array does not have the pixel already
				&& (int) this.getPixel(x,y).colorDistance(ref)<threshold) { // pixel within the threshold
			this.myPixels[this.myCount]=pixelToBeAdded;
			this.myCount++;
			pixelToBeAdded.setColor(newColor); //Once it is on the list, we can change the color, and this
			// may even help keep it from being added again (just to safety net the contains() method.
		}
	}
	
	
	//The almighty Iterator taking three methods, initIterator(), hasNext(), and Next()
	private void initIterator ( ) 
	{	this.myCount=0;
		this.nextIndexToReturn = 0;
		this.myPixels = new Pixel [this.getHeight()*this.getWidth()+10]; //Capacity to hold all the pixels
		this.myPixelsLength=this.getHeight()*this.getWidth()+10; } // in the picture if need-be and extra for error
	private Pixel next ( ) 
	{	Pixel toReturn = this.myPixels[nextIndexToReturn];
		nextIndexToReturn++;
		return toReturn; }
	private boolean hasNext ( ) 
	{	return nextIndexToReturn < this.myCount; }


	///////////////////////// PROJECT 1 ENDS HERE /////////////////////////////

	public boolean equals(Object obj) {
		if (!(obj instanceof Picture)) {
			return false;
		}

		Picture p = (Picture) obj;
		// Check that the two pictures have the same dimensions.
		if ((p.getWidth() != this.getWidth()) ||
				(p.getHeight() != this.getHeight())) {
			return false;
		}

		// Check each pixel.
		for (int x = 0; x < this.getWidth(); x++) {
			for(int y = 0; y < this.getHeight(); y++) {
				if (!this.getPixel(x, y).equals(p.getPixel(x, y))) {
					return false;
				}
			}
		}

		return true;
	}

	/**
	 * Helper method for loading a picture in the current directory.
	 */
	protected static Picture loadPicture(String pictureName) {
		URL url = Picture.class.getResource(pictureName);
		return new Picture(url.getFile().replaceAll("%20", " "));
	}

	/**
	 * Helper method for loading the pictures corresponding to each character
	 * 	for the ASCII art conversion.
	 */
	private static Picture getAsciiPic(int grayValue) {
		int asciiIndex = (int) grayValue / 19;

		if (BMP_AMPERSAND == null) {
			BMP_AMPERSAND = Picture.loadPicture("ampersand.bmp");
			BMP_APOSTROPHE = Picture.loadPicture("apostrophe.bmp");
			BMP_AT = Picture.loadPicture("at.bmp");
			BMP_BAR = Picture.loadPicture("bar.bmp");
			BMP_COLON = Picture.loadPicture("colon.bmp");
			BMP_DOLLAR = Picture.loadPicture("dollar.bmp");
			BMP_DOT = Picture.loadPicture("dot.bmp");
			BMP_EXCLAMATION = Picture.loadPicture("exclamation.bmp");
			BMP_GRAVE = Picture.loadPicture("grave.bmp");
			BMP_HASH = Picture.loadPicture("hash.bmp");
			BMP_PERCENT = Picture.loadPicture("percent.bmp");
			BMP_SEMICOLON = Picture.loadPicture("semicolon.bmp");
			BMP_SPACE = Picture.loadPicture("space.bmp");
		}

		switch(asciiIndex) {
		case 0:
			return Picture.BMP_HASH;
		case 1:
			return Picture.BMP_AT;
		case 2:
			return Picture.BMP_AMPERSAND;
		case 3:
			return Picture.BMP_DOLLAR;
		case 4: 
			return Picture.BMP_PERCENT;
		case 5:
			return Picture.BMP_BAR;
		case 6: 
			return Picture.BMP_EXCLAMATION;
		case 7:
			return Picture.BMP_SEMICOLON;
		case 8:
			return Picture.BMP_COLON;
		case 9: 
			return Picture.BMP_APOSTROPHE;
		case 10:
			return Picture.BMP_GRAVE;
		case 11:
			return Picture.BMP_DOT;
		default:
			return Picture.BMP_SPACE;
		}
	}

	public static void main(String[] args) {
		Picture initialPicture = new Picture(
				FileChooser.pickAFile(FileChooser.OPEN));
		initialPicture.explore();
	}

} // End of Picture class
